#include <GL/glut.h>
#include <iostream>

#include "global_state.h"
#include "selected_state.h"
#include "texture.h"
#include "ui.h"
#include "voxel.h"
#include "screenshot.h"
#include "objloader.h"
#include "objsaver.h"
#include "scene.h"

using namespace std;

int wWidth = 1000;
int wHeight = 960;

Scene *s = new Scene();

vector<ObjLoader> test;
int timeOfTheModel = 0;

TextureManager tm;
GlobalState gs;
GLUI *edit_window = NULL;
SelectedState *ss = NULL;

#define CAMERA_STEP 0.25f
float eye[] = {0, 40, 60};
float center[] = {0, 0, 0};
float mousePrevPos[2] = {-1, -1};
float eyeAngle[] = {270, -45};
float fDist = 2.5;

bool bPersp = true;

// edit_mode = 0, play_edit_mode = 1, play_mode = 2
int mode = 0;

float cood[] = {0, 0, 0};

float translate_x = 0, translate_y = 0, translate_z = 0;
int windowHandle;

void updateCenterPos(void) { // eye is at the center of the sphare
    center[0] = eye[0] + fDist * cos(eyeAngle[0] / 180 * PI);
    center[2] = eye[2] + fDist * sin(eyeAngle[0] / 180 * PI);
    center[1] = eye[1] + fDist * sin(eyeAngle[1] / 180 * PI);
}

void updateView(int width, int height) {
    glViewport(0, 0, width, height); // Reset The Current Viewport

    glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
    glLoadIdentity();            // Reset The Projection Matrix

    float whRatio = (GLfloat)width / (GLfloat)height;
    if (bPersp) {
        gluPerspective(45.0f, whRatio, 0.1f, 1000.0f);
        // glFrustum(-3, 3, -3, 3, 3,100);
    } else {
        glOrtho(-3, 3, -3, 3, -100, 100);
    }

    glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
}

void reshape(int width, int height) {
    if (height == 0) {
        height = 1;
    }
    wWidth = width;
    wHeight = height;

    updateCenterPos();
    updateView(wWidth, wHeight);
}

void Draw() {

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    if (s->getResult() && !s->getStatus()) {

        s->clear();
        glPushMatrix();

        // glTranslatef(0, 10, 0);
        glEnable(GL_TEXTURE_2D);
        tm.bindTexture("pose");

        // glTranslatef(0, 5, 0);
        // glRotatef(180, 1, 0, 0);
        // glRotatef(180,0,1,0);
        // glRotatef(timeOfTheEarth, 0, 1, 0);
        glScalef(0.1, 0.1, 0.1);

        int t = timeOfTheModel / 8;
        test[t % 70 + 30].Draw();
        // test.Draw();

        glDisable(GL_TEXTURE_2D);
        glPopMatrix();
    }

    s->draw();

    if (ss != NULL) {
        ss->modify();
    }

    for (std::vector<Model*>::size_type i = 0; i < gs.model_list.size(); i++) {

        if (gs.model_list[i]->getTexture() == 0) {

            gs.model_list[i]->draw();
        }
        if (gs.model_list[i]->getTexture() == 1) {
            glEnable(GL_TEXTURE_2D);
            tm.bindTexture("wood");
            gs.model_list[i]->draw();
            glDisable(GL_TEXTURE_2D);
        }
        if (gs.model_list[i]->getTexture() == 2) {
            glEnable(GL_TEXTURE_2D);
            tm.bindTexture("zz");
            gs.model_list[i]->draw();
            glDisable(GL_TEXTURE_2D);
        }
    }

    timeOfTheModel++;

    // glActiveTexture(GL_TEXTURE1);
    // tm.bindTexture("specular");
    // DrawQuad();
}

void redraw() {

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glEnable(GL_NORMALIZE);
    glEnable(GL_DEPTH_TEST);
    glShadeModel(GL_SMOOTH);

    gluLookAt(eye[0], eye[1], eye[2], center[0], center[1], center[2], 0, 1, 0);

    // glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    float ambient[] = {0.2f, 0.2f, 0.2f, 1.0f};
    float diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
    float specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
    float position[] = {200.0f, 300.0f, 100.0f, 0.0f};

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);

    // GLfloat light_pos2[] = {20, 20, 20, 1 };
    // glLightfv(GL_LIGHT1, GL_POSITION, light_pos2);
    // glLightfv(GL_LIGHT1, GL_SPECULAR, white);
    // glEnable(GL_LIGHT1);
    // glTranslatef(0.0f, 0.0f,-6.0f);
    // glRotatef(fRotate, 0, 1.0f, 0);
    // glRotatef(-90, 1, 0, 0);
    // glScalef(0.2, 0.2, 0.2);
    Draw();

    glutSwapBuffers();
}

void idle() {
    if (glutGetWindow() != windowHandle)
        glutSetWindow(windowHandle);
    glutPostRedisplay();
}

void init() {

    s->init();
    s->reset();
    tm.registerTexture("./texture/dest_a_top_yes.png", "zz");
    tm.registerTexture("texture/container2.png", "wood");
    tm.registerTexture("texture/Crack.bmp", "crack");
    // tm.registerTexture("container2_specular.png", "specular");

    // tm.registerTexture("texture/world_giant.jpg", "earth");
    tm.registerTexture("texture/peasant_girl_diffuse.png", "pose");

    for (int i = 0; i < 100; i++) {
        string number = to_string(i + 1);
        string filename =
            "object/pose/pose" + string(3 - number.length(), '0') + number;
        filename += ".obj";
        // cout << filename << endl;
        ObjLoader point = ObjLoader(filename);
        test.push_back(point);
    }
}

void mouseMotionCallback(int x, int y) {
    /*
    if (mousePrevPos[0] == -1 && mousePrevPos[1] == -1) {
            mousePrevPos[0] = x;
            mousePrevPos[1] = y;
            glutWarpPointer(wWidth / 2, wHeight / 2);
            return;
    }
    */
    // else {
    eyeAngle[0] += (x - mousePrevPos[0]) * 0.1;
    eyeAngle[1] += -(y - mousePrevPos[1]) * 0.1;
    if (eyeAngle[1] < -90)
        eyeAngle[1] = -90;
    else if (eyeAngle[1] > 90)
        eyeAngle[1] = 90;
    // glutWarpPointer(wWidth / 2, wHeight / 2);
    mousePrevPos[0] = x;
    mousePrevPos[1] = y;
    updateCenterPos();
    //}
}

void mouseKeyCallback(int button, int state, int x, int y) {
    if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
        mousePrevPos[0] = x;
        mousePrevPos[1] = y;
        // glutWarpPointer(wWidth / 2, wHeight / 2);
        return;
    }
}
void key(unsigned char k, int x, int y) {
    if (mode == 0) {
        switch (k) {
        case 27:
        case 'q': {
            exit(0);
            break;
        }
        case 'p': {
            if (ss != NULL) {
                if (ss->texid == 2) {
                    ss->texid = 0;
                } else {
                    ss->texid++;
                }
            }
            break;
        }
        case 'o': {
            mode = 1;
            s->addConveyor(-12, 8, 8, 1);
            s->addConveyor(-12, 6, 8, 1);
            s->addConveyor(-12, 4, 8, 1);
            s->addConveyor(-12, 2, 8, 1);
            s->addConveyor(-12, 0, 8, 2);
            s->addConveyor(-10, 0, 8, 2);
            s->addConveyor(-8, 0, 8, 2);
            s->addConveyor(-6, 0, 8, 2);
            // s->addConveyor(-4,0,8,2);
            s->addConveyor(-2, 0, 8, 2);
            s->addConveyor(0, 0, 8, 2);
            s->addConveyor(2, 0, 8, 2);
            s->addConveyor(4, 0, 8, 2);
            s->addConveyor(6, 0, 8, 2);
            s->addConveyor(8, 0, 8, 2);
            s->addConveyor(10, 0, 8, 2);
            s->addConveyor(12, 0, 6, 2);
            s->addConveyor(14, 0, 2, 2);
            s->addConveyor(16, 0, 0, 2);
            s->addConveyor(18, 0, 0, 3);
            s->addConveyor(18, 2, 0, 3);
            s->addConveyor(18, 4, 0, 3);
            s->addConveyor(18, 6, 0, 3);
            s->addConveyor(18, 8, 0, 3);
            s->addConveyor(18, 10, 0, 3);
            s->addConveyor(18, 12, 0, 3);
            s->addConveyor(18, 14, 0, 3);

            s->addConveyor(-4, 8, 0, 1);
            s->addConveyor(-4, 6, 0, 1);
            s->addConveyor(-4, 4, 0, 1);
            s->addConveyor(-4, 2, 0, 1);
            s->addConveyor(-4, 0, 0, 1);
            s->addConveyor(-4, -2, 0, 1);
            s->addConveyor(-4, -4, 0, 1);
            s->addConveyor(-4, -6, 0, 1);
            s->addConveyor(-4, -8, 0, 1);
            s->addConveyor(-4, -8, 0, 1);
            s->addConveyor(-4, -10, 0, 1);
            s->addConveyor(-4, -12, 0, 1);
            s->addConveyor(-4, -14, 0, 1);
            s->addConveyor(-4, -16, 0, 1);
            s->addConveyor(-4, -18, 0, 1);

            s->addConveyor(4, 8, 4, 1);
            s->addConveyor(4, 6, 4, 1);
            s->addConveyor(4, 4, 4, 1);
            s->addConveyor(4, 2, 4, 1);
            s->addConveyor(4, 0, 4, 1);
            s->addConveyor(4, -2, 4, 1);
            s->addConveyor(4, -4, 4, 1);
            s->addConveyor(4, -6, 4, 1);
            s->addConveyor(4, -8, 4, 1);
            s->addConveyor(4, -10, 4, 0);
            s->addConveyor(2, -10, 4, 0);
            s->addConveyor(0, -10, 4, 0);
            s->addConveyor(-2, -10, 4, 0);
            s->addConveyor(-4, -10, 4, 0);
            s->addConveyor(-6, -10, 4, 0);
            s->addConveyor(-8, -10, 2, 0);
            s->addConveyor(-10, -10, 0, 0);
            s->addConveyor(-12, -10, 0, 0);
            s->addConveyor(-14, -10, 0, 1);
            s->addConveyor(-14, -12, 0, 1);
            s->addConveyor(-14, -14, 0, 1);
            s->addConveyor(-14, -16, 0, 1);
            s->addConveyor(-14, -18, 0, 1);
            s->addConveyor(-14, -20, 0, 2);
            break;
        }
        /*
        case 'a': {
                eye[0] -= 0.5;
                center[0] -= 0.5;
                break;
        }
        case 'd': {
                eye[0] += 0.5;
                center[0] += 0.5;
                break;
        }
        case 'w': {
                eye[2] -= 0.5;
                center[2] -= 0.5;
                break;
        }
        case 's': {
                eye[2] += 0.5;
                center[2] += 0.5;
                break;
        }
        case 'z': {
                eye[1] += 0.5;
                center[1] += 0.5;
                break;
        }
        case 'c': {
                eye[1] -= 0.5;
                center[1] -= 0.5;
                break;
        }*/
        case '0': {
            translate_x = 0, translate_y = 0, translate_z = 0;
            Model *m = new Cube();
            gs.model_list.push_back(m);
            gs.selected = gs.model_list.size() - 1;
            break;
        }
        case '1': {
            translate_x = 0, translate_y = 0, translate_z = 0;
            Model *m = new Sphere();
            gs.model_list.push_back(m);
            gs.selected = gs.model_list.size() - 1;
            break;
        }
        case '2': {
            translate_x = 0, translate_y = 0, translate_z = 0;
            Model *m = new Cylinder();
            gs.model_list.push_back(m);
            gs.selected = gs.model_list.size() - 1;
            break;
        }
        case '3': {
            translate_x = 0, translate_y = 0, translate_z = 0;
            Model *m = new Cone();
            gs.model_list.push_back(m);
            gs.selected = gs.model_list.size() - 1;
            break;
        }
        case '4': {
            translate_x = 0, translate_y = 0, translate_z = 0;
            Model *m = new Pyramid(12);
            gs.model_list.push_back(m);
            gs.selected = gs.model_list.size() - 1;
            break;
        }
        case '5': {
            translate_x = 0, translate_y = 0, translate_z = 0;
            Model *m = new Prism(5);
            gs.model_list.push_back(m);
            gs.selected = gs.model_list.size() - 1;
            break;
        }

        case 9: {
            if (gs.model_list.size() == 0) {
                gs.selected = -1;
            } else {
                if ((std::vector<Model*>::size_type)gs.selected == gs.model_list.size() - 1) {
                    gs.selected = 0;
                } else {
                    gs.selected++;
                }
            }
            break;
        }
        case 'g':
            grab(wWidth, wHeight);
            break;
        case 'h':
            // grab(wWidth, wHeight);
            // objsave(test.vSets, test.vtSets, test.vnSets, test.fSets);
            break;
        }

        // cout << k << endl;

        if ((k >= '0' && k <= '5') || k == 9) {
            if (gs.selected != -1) {
                if (edit_window != NULL) {
                    edit_window->close();
                }
                if (ss != NULL) {
                    delete ss;
                }

                ss = new SelectedState(gs.model_list[gs.selected]);
                edit_window = createEditWindow(ss);
            }
        }
        cout << "current selected:" << gs.selected << endl;
    } else if (mode == 1) {
        cout << cood[0] << " " << cood[1] << " " << cood[2] << endl;
        switch (k) {
        case ' ':
            cood[0] = 0;
            cood[1] = 0;
            cood[2] = 0;
            s->addConveyor(cood[0], cood[1], cood[2], 1);
            break;
        case 'j':
            s->delConveyor(cood[0], cood[1], cood[2]);

            if (!s->checkObjectHit(cood[0] - 2, cood[1], cood[2])) {
                cood[0] -= 2;
                s->addConveyor(cood[0], cood[1], cood[2], 1);
            } else {
                s->addConveyor(cood[0], cood[1], cood[2], 1);
            }
            break;
        case 'l':
            s->delConveyor(cood[0], cood[1], cood[2]);
            if (!s->checkObjectHit(cood[0] + 2, cood[1], cood[2])) {
                cood[0] += 2;
                s->addConveyor(cood[0], cood[1], cood[2], 1);
            } else {
                s->addConveyor(cood[0], cood[1], cood[2], 1);
            }
            break;
        case 'i':
            s->delConveyor(cood[0], cood[1], cood[2]);
            if (!s->checkObjectHit(cood[0], cood[1] + 2, cood[2])) {
                cood[1] += 2;
                s->addConveyor(cood[0], cood[1], cood[2], 1);
            } else {
                s->addConveyor(cood[0], cood[1], cood[2], 1);
            }
            break;
        case 'k':
            s->delConveyor(cood[0], cood[1], cood[2]);
            if (!s->checkObjectHit(cood[0], cood[1] - 2, cood[2])) {
                cood[1] -= 2;
                s->addConveyor(cood[0], cood[1], cood[2], 1);
            } else {
                s->addConveyor(cood[0], cood[1], cood[2], 1);
            }
            break;
        case 'n':
            s->delConveyor(cood[0], cood[1], cood[2]);
            if (!s->checkObjectHit(cood[0], cood[1], cood[2] - 2)) {
                cood[2] -= 2;
                s->addConveyor(cood[0], cood[1], cood[2], 1);
            } else {
                s->addConveyor(cood[0], cood[1], cood[2], 1);
            }
            break;
        case 'm':
            s->delConveyor(cood[0], cood[1], cood[2]);
            if (!s->checkObjectHit(cood[0], cood[1], cood[2] + 2)) {
                cood[2] += 2;
                s->addConveyor(cood[0], cood[1], cood[2], 1);
            } else {
                s->addConveyor(cood[0], cood[1], cood[2], 1);
            }
            break;
        case 'u':
            s->rotConveyor(cood[0], cood[1], cood[2], 1);
            break;
        case 'p':
            s->rotConveyor(cood[0], cood[1], cood[2], 0);
            break;
        case 'o': {
            mode = 2;
            s->start();
            break;
        }
        }
    } else if (mode == 2) {
        switch (k) {
        case 'o': {
            mode = 0;
            s->reset();
            break;
        }
        }
    }

    double eye_tmp[3];
    eye_tmp[0] = eye[0];
    eye_tmp[1] = eye[1];
    eye_tmp[2] = eye[2];

    switch (k) {
    case 'a':
        eye_tmp[0] += CAMERA_STEP * cos((eyeAngle[0] - 90) / 180 * PI);
        eye_tmp[2] += CAMERA_STEP * sin((eyeAngle[0] - 90) / 180 * PI);
        break;
    case 'd':
        eye_tmp[0] += CAMERA_STEP * cos((eyeAngle[0] + 90) / 180 * PI);
        eye_tmp[2] += CAMERA_STEP * sin((eyeAngle[0] + 90) / 180 * PI);
        break;
    case 'w':
        eye_tmp[0] += CAMERA_STEP * cos(eyeAngle[0] / 180 * PI);
        eye_tmp[2] += CAMERA_STEP * sin(eyeAngle[0] / 180 * PI);
        break;
    case 's':
        eye_tmp[0] += CAMERA_STEP * cos((eyeAngle[0] + 180) / 180 * PI);
        eye_tmp[2] += CAMERA_STEP * sin((eyeAngle[0] + 180) / 180 * PI);
        break;
    case 'z':
        eye_tmp[1] -= CAMERA_STEP;
        break;
    case 'c':
        eye_tmp[1] += CAMERA_STEP;
        break;
    }

    bool flag = true;
    for (std::vector<Model*>::size_type i = 0; i < gs.model_list.size(); i++) {
        if (gs.model_list[i]->isHit(eye_tmp[0], eye_tmp[2], eye_tmp[1])) {
            flag = false;
            break;
        }
    }

    if (!s->checkCameraHit(eye_tmp[0], eye_tmp[2], eye_tmp[1]) && flag) {
        eye[0] = eye_tmp[0];
        eye[1] = eye_tmp[1];
        eye[2] = eye_tmp[2];
        updateCenterPos();
    } else {
        // cout << "hit" << endl;
    }
}
int main(int argc, char *argv[]) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
    glutInitWindowSize(1000, 960);
    windowHandle = glutCreateWindow("PIPELINE SIM");
    const GLubyte *OpenGLVersion = glGetString(GL_VERSION);
    cout << "version " << OpenGLVersion << endl;
    glutDisplayFunc(redraw);
    glutSetCursor(GLUT_CURSOR_CROSSHAIR);
    glutMotionFunc(mouseMotionCallback);
    glutMouseFunc(mouseKeyCallback);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(key);
    // glutIdleFunc(idle);
    init();
    GLUI_Master.set_glutIdleFunc(idle);
    glutMainLoop();
    return 0;
}
